﻿using System;
using System.Collections.Generic;
using Jayden.Dll.Cards.Poker.Enums;
using Jayden.Dll.Core.Persistence;

namespace Jayden.Dll.Cards.Poker
{
	public class TexasHoldemHandSummaryLegacy
	{
		public class SeatInformation
		{
			public int Number;
			public string PlayerName;
			public ulong PlayerStack;

			public override string ToString()
			{
				return string.Format("Seat #{0}: {1} ({2})", Number, PlayerName, PlayerStack);
			}
		}
		public class ActionInformation
		{
			public string PlayerName;
			public PokerActionType ActionType = PokerActionType.Fold;
			public ulong Amount = 0;

			public override string ToString()
			{
				if (Amount > 0)
					return string.Format("{0} {1} ({2})", PlayerName, ActionType, Amount);
				return string.Format("{0} {1}", PlayerName, ActionType);
			}
		}

		public string ProviderName;
		public string HandReference;
		public string TableName;
		public DateTime DateTime;
		public uint ButtonNumber;
		public PokerPot Pot;
		public ulong TotalPot;
		public ulong Rake;
		public bool Showdown = false;
		public ulong SmallBlind;
		public ulong BigBlind;
		public string Currency;
		public uint SeatCount;

		public PokerStreetType Street;
		public List<ActionInformation> PreFlopActions;
		public List<ActionInformation> FlopActions;
		public List<ActionInformation> TurnActions;
		public List<ActionInformation> RiverActions;
		public List<ActionInformation> ShowdownActions;

		public List<KeyValuePair<string, ulong>> Winnings = new List<KeyValuePair<string, ulong>>();

		public List<SeatInformation> Seats = new List<SeatInformation>();
		public Dictionary<string, CardList> HoleCards = new Dictionary<string, CardList>();
		public Dictionary<string, PokerHighHand> HandsResult = new Dictionary<string, PokerHighHand>();
		public Dictionary<string, PokerStreetType> FoldStreet = new Dictionary<string, PokerStreetType>();
		public Dictionary<string, long> Balance = new Dictionary<string, long>();
		public CardList CommunityCards;

		public bool IsDealer(string playerName)
		{
			foreach (SeatInformation seat in Seats)
				if (seat.PlayerName.Equals(playerName))
					return seat.Number == ButtonNumber;
			return false;
		}
		public bool IsNthToAct(string playerName, int actCountCheck)
		{
			List<SeatInformation> left = new List<SeatInformation>();
			bool started = false;
			int actCount = 0;
			foreach (SeatInformation seat in Seats)
			{
				if (started)
				{
					if (actCountCheck == actCount)
						return seat.PlayerName.Equals(playerName);
					actCount++;
				}
				else
				{
					if (seat.Number == ButtonNumber)
						started = true;
					left.Add(seat);
				}
			}
			foreach (SeatInformation seat in left)
			{
				if (actCountCheck == actCount)
					return seat.PlayerName.Equals(playerName);
				actCount++;
			}
			throw new Exception("Invalid player name");
		}
		public int GetPosition(string playerName)
		{
			List<SeatInformation> left = new List<SeatInformation>();
			bool started = false;
			int actCount = 0;
			foreach (SeatInformation seat in Seats)
			{
				if (started)
				{
					if (seat.PlayerName.Equals(playerName))
						return actCount;
					actCount++;
				}
				else
				{
					if (seat.Number == ButtonNumber)
						started = true;
					left.Add(seat);
				}
			}
			foreach (SeatInformation seat in left)
			{
				if (seat.PlayerName.Equals(playerName))
					return actCount;
				actCount++;
			}
			throw new Exception("Invalid player name");
		}
		public bool IsBigBlind(string playerName)
		{
			bool nth = IsNthToAct(playerName, 1);
#if CHECK
			if (nth)
			{
				int index = 0;
				while (PreFlopActions[index].ActionType == PokerActionType.Ante)
					index++;
				if (PreFlopActions[index + 1].ActionType != PokerActionType.BigBlind||
					PreFlopActions[index + 1].PlayerName != playerName)
					throw new Exception("Invalid nth function checks.");
			}
#endif
			return nth;
		}
		public bool IsSmallBlind(string playerName)
		{
			bool nth = IsNthToAct(playerName, 0);
#if CHECK
			if (nth)
			{
				int index = 0;
				while (PreFlopActions[index].ActionType == PokerActionType.Ante)
					index++;
				if (PreFlopActions[index].ActionType != PokerActionType.SmallBlind ||
					PreFlopActions[index].PlayerName != playerName)
					throw new Exception("Invalid nth function checks.");
			}
#endif
			return nth;
		}
		public bool IsUnderTheGun(string playerName)
		{
			bool nth = IsNthToAct(playerName, 2);
#if CHECK
			if (nth)
			{
				int index = 2;
				while (PreFlopActions[index].ActionType == PokerActionType.SmallBlind || PreFlopActions[index].ActionType == PokerActionType.BigBlind || PreFlopActions[index].ActionType == PokerActionType.BothBlinds || PreFlopActions[index].ActionType == PokerActionType.Ante)
					index++;
				if (PreFlopActions[index].PlayerName != playerName)
					throw new Exception("Invalid nth function checks.");
			}
#endif
			return nth;
		}

		public bool IsAllInPreFlop(string playerName)
		{
			int seatNumber = GetPlayerSeatNumber(playerName);
			ulong stack = GetPlayerStartingStack(seatNumber);
			ulong preflopInThePot = 0;
			foreach(ActionInformation action in PreFlopActions)
				if (action.PlayerName == playerName)
				{
					if (action.Amount != 0)
						preflopInThePot += action.Amount;
					else if (action.ActionType == PokerActionType.Fold)
						return false;
				}
			return preflopInThePot >= stack;
		}
		public bool IsAllIn(string playerName)
		{
			int seatNumber = GetPlayerSeatNumber(playerName);
			ulong stack = GetPlayerStartingStack(seatNumber);
			ulong preflopInThePot = 0;
			foreach (ActionInformation action in PreFlopActions)
				if (action.PlayerName == playerName)
				{
					if (action.Amount != 0)
						preflopInThePot += action.Amount;
					else if (action.ActionType == PokerActionType.Fold)
						return false;
				}
			if(FlopActions != null)
				foreach (ActionInformation action in FlopActions)
					if (action.PlayerName == playerName)
					{
						if (action.Amount != 0)
							preflopInThePot += action.Amount;
						else if (action.ActionType == PokerActionType.Fold)
							return false;
					}
			if(TurnActions != null)
				foreach (ActionInformation action in TurnActions)
					if (action.PlayerName == playerName)
					{
						if (action.Amount != 0)
							preflopInThePot += action.Amount;
						else if (action.ActionType == PokerActionType.Fold)
							return false;
					}
			if(RiverActions != null)
				foreach (ActionInformation action in RiverActions)
					if (action.PlayerName == playerName)
					{
						if (action.Amount != 0)
							preflopInThePot += action.Amount;
						else if (action.ActionType == PokerActionType.Fold)
							return false;
					}
			return preflopInThePot >= stack;
		}

		public bool HasFlop { get { return CommunityCards != null && CommunityCards.Count >= 3; } }
		public bool HasTurn { get { return CommunityCards != null && CommunityCards.Count >= 4; } }
		public bool HasRiver { get { return CommunityCards != null && CommunityCards.Count == 5; } }

		public long GetBalance(string playerName)
		{
			long balance = 0;
			if (Balance.TryGetValue(playerName, out balance))
				return balance;
			// should threadsafe this
			balance = 0 - (long)Pot.GetTotalAmountForPlayer(playerName);
			for (int c = 0; c < Winnings.Count; c++)
				if (Winnings[c].Key == playerName)
					balance += (long)Winnings[c].Value;
			Balance.Add(playerName, balance);
			return balance;
		}

		public void SetSeat(int seatNumber, string playerName, double playerStack)
		{
			SeatInformation info = new SeatInformation();
			info.Number = seatNumber;
			info.PlayerName = playerName;
			info.PlayerStack = (ulong)Math.Round(playerStack * 100);
			Seats.Add(info);
		}
		public string GetPlayerName(int seatNumber)
		{
			foreach (SeatInformation seat in Seats)
				if (seat.Number == seatNumber)
					return seat.PlayerName;
			return string.Empty;
		}
		public int GetPlayerSeatNumber(string playerName)
		{
			foreach (SeatInformation seat in Seats)
				if (seat.PlayerName == playerName)
					return seat.Number;
			throw new Exception("Player is not playing at the table.");
		}
		public ulong GetPlayerStartingStack(int seatNumber)
		{
			foreach (SeatInformation seat in Seats)
				if (seat.Number == seatNumber)
					return seat.PlayerStack;
			return 0;
		}
		public ulong GetPlayerStack(int seatNumber)
		{
			foreach (SeatInformation seat in Seats)
				if (seat.Number == seatNumber)
					return seat.PlayerStack - Pot.GetTotalAmountForPlayer(seat.PlayerName);
			return 0;
		}
		public void ReturnUncalledBet(string playerName, double amount)
		{
			Pot.ReturnUncalledBet(playerName, (ulong)Math.Round(amount * 100));
		}
		public void SetStreet(PokerStreetType street, params Card[] newCards)
		{
			Street = street;
			switch (Street)
			{
				case PokerStreetType.PreFlop:
					Pot = new PokerPot();
					PreFlopActions = new List<ActionInformation>();
					break;
				case PokerStreetType.Flop:
					FlopActions = new List<ActionInformation>();
					break;
				case PokerStreetType.Turn:
					TurnActions = new List<ActionInformation>();
					break;
				case PokerStreetType.River:
					RiverActions = new List<ActionInformation>();
					break;
				case PokerStreetType.Showdown:
					ShowdownActions = new List<ActionInformation>();
					break;
			}
			if (newCards != null && newCards.Length > 0)
			{
				if (CommunityCards == null)
					CommunityCards = new CardList();
				CommunityCards.AddRange(newCards);
			}
		}
		public void AddAction(string playerName, PokerActionType action, double amount = 0.0)
		{
			if (action == PokerActionType.Fold)
				FoldStreet.Add(playerName, Street);
			ulong uAmount = (ulong)Math.Round(amount * 100);
			if (action == PokerActionType.Raise)
				uAmount += Pot.GetCallAmountForPlayer(playerName);
			switch (Street)
			{
				case PokerStreetType.PreFlop:
					PreFlopActions.Add(new ActionInformation() { PlayerName = playerName, ActionType = action, Amount = uAmount });
					break;
				case PokerStreetType.Flop:
					FlopActions.Add(new ActionInformation() { PlayerName = playerName, ActionType = action, Amount = uAmount });
					break;
				case PokerStreetType.Turn:
					TurnActions.Add(new ActionInformation() { PlayerName = playerName, ActionType = action, Amount = uAmount });
					break;
				case PokerStreetType.River:
					RiverActions.Add(new ActionInformation() { PlayerName = playerName, ActionType = action, Amount = uAmount });
					break;
				case PokerStreetType.Showdown:
					ShowdownActions.Add(new ActionInformation() { PlayerName = playerName, ActionType = action, Amount = uAmount });
					break;
			}
			if (action == PokerActionType.BothBlinds)
			{
				ulong claimable = Pot.GetCallAmountForPlayer(playerName);
				Pot.Add(playerName, claimable);
				Pot.Add("__dead_both__\t" + playerName, uAmount - claimable);
			}
			else if (action == PokerActionType.SmallBlind && !Pot.OnlyAnte)
				Pot.Add("__dead_small__\t" + playerName, uAmount);
			else if (action == PokerActionType.Ante)
				Pot.Add(playerName, uAmount);
			else if (uAmount > 0)
			{
				if (action == PokerActionType.SmallBlind && !Pot.Empty && Pot.OnlyAnte)
				{
					for (int s = 0; s < Seats.Count; s++)
					{
						bool found = false;
						for (int c = 0; c < PreFlopActions.Count - 1 && !found; c++)
							found = PreFlopActions[c].PlayerName == Seats[s].PlayerName;
						if (!found)
						{
							Seats.RemoveAt(s);
							s--;
						}
					}
				}
				Pot.Add(playerName, uAmount);
			}
		}
		public void SetHoleCards(string playerName, Card[] cards)
		{
			CardList already;
			if (!HoleCards.TryGetValue(playerName, out already))
				HoleCards.Add(playerName, new CardList(cards));
			else if (already.Count < cards.Length)
				HoleCards[playerName] = new CardList(cards);
		}

		public bool VPIP(string playerName)
		{
			if (PreFlopActions != null && PreFlopActions.Count > 0)
				foreach (ActionInformation action in PreFlopActions)
					if (action.PlayerName.Equals(playerName))
					{
						if (action.ActionType == PokerActionType.Bet ||
							action.ActionType == PokerActionType.Call ||
							action.ActionType == PokerActionType.Raise)
							return true;
						else if (action.ActionType == PokerActionType.Fold)
							return false;
					}
			return false;
		}
		public bool CCPF(string playerName)
		{
			bool result = false;
			if (PreFlopActions != null && PreFlopActions.Count > 0)
				foreach (ActionInformation action in PreFlopActions)
					if (action.PlayerName.Equals(playerName))
					{
						if (action.ActionType == PokerActionType.Call)
							return result;
						else
							return false;
					}
					else if (action.ActionType == PokerActionType.Bet ||
					 action.ActionType == PokerActionType.Raise)
						result = true;
			return false;
		}
		public bool PFR(string playerName)
		{
			if (PreFlopActions != null && PreFlopActions.Count > 0)
				foreach (ActionInformation action in PreFlopActions)
					if (action.PlayerName.Equals(playerName))
					{
						if (action.ActionType == PokerActionType.Bet ||
							action.ActionType == PokerActionType.Raise)
							return true;
						else if (action.ActionType == PokerActionType.Fold)
							return false;
					}
			return false;
		}
		public bool CBet(string playerName, PokerStreetType street)
		{
			List<ActionInformation> actions = null;
			if (street == PokerStreetType.Flop)
				actions = FlopActions;
			else if (street == PokerStreetType.Turn)
				actions = TurnActions;
			else if (street == PokerStreetType.River)
				actions = RiverActions;
			if (actions == null)
				return false;
			foreach (ActionInformation action in actions)
				if (action.ActionType == PokerActionType.Bet ||
					action.ActionType == PokerActionType.Raise)
					return action.PlayerName == playerName;
			return false;
		}
		public bool Bet3(string playerName)
		{
			int raiseCount = 0;
			if (PreFlopActions != null && PreFlopActions.Count > 0)
				foreach (ActionInformation action in PreFlopActions)
					if (action.PlayerName.Equals(playerName))
					{
						if (action.ActionType == PokerActionType.Bet)
							raiseCount++;
						else if (action.ActionType == PokerActionType.Raise)
						{
							if (raiseCount >= 1)
								return true;
							raiseCount++;
						}
						else if (action.ActionType == PokerActionType.Fold)
							return false;
					}
					else if (action.ActionType == PokerActionType.Bet || action.ActionType == PokerActionType.Raise)
						raiseCount++;
			return false;
		}
		public bool Bet4(string playerName)
		{
			int raiseCount = 0;
			if (PreFlopActions != null && PreFlopActions.Count > 0)
				foreach (ActionInformation action in PreFlopActions)
					if (action.PlayerName.Equals(playerName))
					{
						if (action.ActionType == PokerActionType.Bet)
							raiseCount++;
						else if (action.ActionType == PokerActionType.Raise)
						{
							if (raiseCount >= 2)
								return true;
							raiseCount++;
						}
						else if (action.ActionType == PokerActionType.Fold)
							return false;
					}
					else if (action.ActionType == PokerActionType.Bet || action.ActionType == PokerActionType.Raise)
						raiseCount++;
			return false;
		}
		public bool SF(string playerName)
		{
			PokerStreetType street;
			if (FoldStreet.TryGetValue(playerName, out street))
				if (street == PokerStreetType.PreFlop)
					return false;
			return true;
		}
		public bool WTSD(string playerName)
		{
			if(HasRiver && Showdown && !FoldStreet.ContainsKey(playerName))
				return true;
			return false;
		}
		public bool Won(string playerName)
		{
			long balance = GetBalance(playerName);
			if (balance > 0)
				return true;
			return false;
		}

		public void ComputeStatistics(UnitOfWork uow, Dictionary<string, TexasHoldemPlayerStatistics> statistics, bool markNew = true, bool loadFromDb = false)
		{
			TexasHoldemHandSummaryLegacy table = this;
			TexasHoldemPlayerStatisticsDataAccess dataAccess = uow.DataAccessManager.GetDataAccess<TexasHoldemPlayerStatistics>() as TexasHoldemPlayerStatisticsDataAccess;
			foreach (TexasHoldemHandSummaryLegacy.SeatInformation seat in table.Seats)
			{
				TexasHoldemPlayerStatistics stat = null;
				string key = seat.PlayerName + table.BigBlind + table.Currency;
				if (!statistics.TryGetValue(key, out stat))
				{
					if (loadFromDb)
						stat = dataAccess.Read(table.ProviderName, seat.PlayerName, table.BigBlind, table.Currency);
					if (stat == null)
					{
						stat = new TexasHoldemPlayerStatistics(uow);
						stat.ProviderName = table.ProviderName;
						stat.PlayerName = seat.PlayerName;
						stat.BigBlind = table.BigBlind;
						stat.Currency = table.Currency;
					}
					statistics.Add(key, stat);
					if(markNew)
						uow.MarkNew(stat);
				}
				stat.HandCount++;
				if(stat.LastChanges.CompareTo(table.DateTime) < 0)
					stat.LastChanges = table.DateTime;
				stat.PlayerBalance += table.GetBalance(seat.PlayerName);
				bool won = false;
				if (table.Won(stat.PlayerName))
				{
					stat.Won++;
					won = true;
				}
				if (table.VPIP(seat.PlayerName))
				{
					stat.VPIP++;
					CardList cards;
					if (table.HoleCards.TryGetValue(seat.PlayerName, out cards) && cards.Count == 2)
					{
						TexasHoldemHandCategory categ = new TexasHoldemHandCategory(cards[0], cards[1]);
						stat.HandRangeList.AddOrJoin(new TexasHoldemHandRange(categ));
					}
					if (table.CCPF(seat.PlayerName))
						stat.CCPF++;
				}
				if (table.Won(stat.PlayerName))
					stat.Won++;
				if (table.WTSD(stat.PlayerName))
				{
					stat.WTSD++;
					if (won)
						stat.WSD++;
				}
				bool isRaiser = false;
				if (table.PFR(stat.PlayerName))
				{
					isRaiser = true;
					stat.PFR++;
					if (table.Bet3(stat.PlayerName))
						stat.Bet3++;
					else if (table.Bet4(stat.PlayerName))
						stat.Bet4++;
				}
				if (table.SF(seat.PlayerName))
				{
					stat.SF++;
					if (won)
						stat.WWSF++;
					if (isRaiser && table.CBet(seat.PlayerName, PokerStreetType.Flop))
						stat.CBetFlop++;
				}
			}
		}
	}
}
